package com.xxatom.ha.common.retry;

import java.util.concurrent.TimeUnit;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

/**
 * This is used to count the number of retries.
 *
 * The waiting time during two retries is determined by the specified {@link BackoffPolicy}, default
 * is {@link ExponentialBackoffPolicy}.
 *
 * Stop retrying if the maximum number of retries <code>maxAttempts<code/>is reached, call method
 * #shouldRetry to determine whether another retry is necessary.
 *
 * @author Alex
 */
public class RetryCounter {

  @Getter
  @Builder
  @AllArgsConstructor
  public static class RetryConfig {

    private int maxAttempts;
    private long sleepInterval;
    private TimeUnit timeUnit;
    private BackoffPolicy backoffPolicy;

    RetryConfig() {
      maxAttempts = 1;
      sleepInterval = 1000;
      timeUnit = TimeUnit.MILLISECONDS;
      backoffPolicy = new ExponentialBackoffPolicy();
    }
  }

  public static class BackoffPolicy {

    public long getBackoffTime(final RetryConfig config, final int attempts) {
      return config.getSleepInterval();
    }
  }

  public static class ExponentialBackoffPolicy extends BackoffPolicy {

    @Override
    public long getBackoffTime(final RetryConfig config, final int attempts) {
      return (long) (config.getSleepInterval() * Math.pow(2, attempts));
    }
  }

  private final RetryConfig retryConfig;
  private final static int MAX_ATTEMPTS = 3;

  private int attempts;

  RetryCounter(final RetryConfig retryConfig) {
    this.retryConfig = retryConfig;
  }

  public void sleepUntilNextRetry() throws InterruptedException {
    long sleepTime = retryConfig.backoffPolicy.getBackoffTime(retryConfig, attempts);
    retryConfig.getTimeUnit().sleep(sleepTime);
    attempts++;
  }

  public boolean shouldRetry() {
    return attempts < retryConfig.getMaxAttempts() && attempts < MAX_ATTEMPTS;
  }

}
